#ifndef __POINT_3_HPP__
#define __POINT_3_HPP__

#include <stdint.h>
#include <assert.h>

#define USE_NLOHMANN_JSON 1
#define USE_FMT_TO_STRING 1

#if USE_NLOHMANN_JSON
#include "nlohmann/json.hpp"
#endif
#if USE_FMT_TO_STRING
#include "spdlog/fmt/fmt.h"
#endif

namespace PocketSurvival::Engine
{
    struct Point3
    {
        int32_t posX;
        int32_t posY;
        int32_t posZ;

        constexpr Point3() noexcept;
        constexpr Point3(int32_t value) noexcept;
        constexpr Point3(int32_t x, int32_t y, int32_t z) noexcept;

        inline int64_t toKey() const;

        constexpr inline Point3 &operator+=(const Point3 &left);
        constexpr inline Point3 &operator-=(const Point3 &left);
        constexpr inline Point3 &operator*=(int32_t c);
        constexpr inline Point3 &operator/=(int32_t c);

        friend constexpr inline bool operator==(const Point3 &left, const Point3 &right)
        {
            return (left.posX == right.posX) && (left.posY == right.posY) && (left.posZ == right.posZ);
        }
        friend constexpr inline bool operator!=(const Point3 &left, const Point3 &right)
        {
            return !(left == right);
        }
        friend constexpr inline Point3 operator+(const Point3 &left, const Point3 &right)
        {
            return Point3(left.posX + right.posX, left.posY + right.posY, left.posZ + right.posZ);
        }
        friend constexpr inline Point3 operator-(const Point3 &left, const Point3 &right)
        {
            return Point3(left.posX - right.posX, left.posY - right.posY, left.posZ - right.posZ);
        }
        friend constexpr inline Point3 operator*(const Point3 &left, int32_t c)
        {
            return Point3(left.posX * c, left.posY * c, left.posZ * c);
        }
        friend constexpr inline Point3 operator*(int32_t c, const Point3 &right)
        {
            return Point3(right.posX * c, right.posY * c, right.posZ * c);
        }
        friend constexpr inline Point3 operator+(const Point3 &left)
        {
            return left;
        }
        friend constexpr inline Point3 operator-(const Point3 &left)
        {
            return Point3(-left.posX, -left.posY, -left.posZ);
        }
        friend constexpr inline Point3 operator/(const Point3 &left, int32_t divisor)
        {
            assert(divisor < 0 || divisor > 0);
            return Point3(left.posX / divisor, left.posY / divisor, left.posZ / divisor);
        }
        inline bool operator<(const Point3& other) const
        {
            if(posX < other.posX)
            {
                return true;
            }
            else if(posX == other.posX)
            {
                if(posY < other.posY)
                {
                    return true;
                }
                else if(posY == other.posY)
                {
                    if(posZ < other.posZ)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        inline size_t operator()(const Point3& point) const
        {
            return point.toKey();
        }
        inline bool operator()(const Point3& left, const Point3 &right) const noexcept
        {
            return (left == right);
        }

#if USE_NLOHMANN_JSON
        friend inline void to_json(nlohmann::json &json, const Point3 &vec);
        friend inline void from_json(const nlohmann::json &json, Point3 &vec);
#endif

#if USE_FMT_TO_STRING
        inline std::string toString() const
        {
            return fmt::format("{},{},{}",posX,posY,posZ);
        }
#endif

    };

    constexpr inline Point3::Point3() noexcept : posX(0), posY(0), posZ(0) {}
    constexpr inline Point3::Point3(int32_t value) noexcept : posX(value), posY(value), posZ(value) {}
    constexpr inline Point3::Point3(int32_t x, int32_t y, int32_t z) noexcept : posX(x), posY(y), posZ(z) {}

    inline int64_t Point3::toKey() const
    {
        union
        {
            char buff[8];
            struct
            {
                uint16_t aa;
                uint16_t ab;
                uint16_t ba;
                uint16_t bb;
            };
            int64_t int64_value;
        } byte8Data;
        
        byte8Data.buff[0] = ((posX >> 15) & 0xFF);
        byte8Data.buff[1] = ((posZ >> 15) & 0xFF);
        byte8Data.ab = posX;
        byte8Data.ba = posZ;
        byte8Data.bb = posY;
        return byte8Data.int64_value;
    }


    constexpr inline Point3 &Point3::operator+=(const Point3 &right)
    {
        posX += right.posX;
        posY += right.posY;
        posZ += right.posZ;
        return *this;
    }

    constexpr inline Point3 &Point3::operator-=(const Point3 &right)
    {
        posX -= right.posX;
        posY -= right.posY;
        posZ -= right.posZ;
        return *this;
    }

    constexpr inline Point3 &Point3::operator*=(int32_t c)
    {
        posX *= c;
        posY *= c;
        posZ *= c;
        return *this;
    }

    constexpr inline Point3 &Point3::operator/=(int32_t divisor)
    {
        assert(divisor > 0 || divisor < 0);
        posX /= divisor;
        posY /= divisor;
        posZ /= divisor;
        return *this;
    }


#if USE_NLOHMANN_JSON

    inline void to_json(nlohmann::json &json, const Point3 &vec)
    {
        json = {{"X",vec.posX},{"Y",vec.posY},{"Z",vec.posZ}};
    }
    inline void from_json(const nlohmann::json &json, Point3 &vec)
    {
        vec.posX = json["X"].get<int32_t>();
        vec.posY = json["Y"].get<int32_t>();
        vec.posZ = json["Z"].get<int32_t>();
    }

#endif

}

#endif
